package com.code.soulnetty.modbus.biz;

import com.code.soulnetty.modbus.client.service.DataType;
import com.ql.util.express.DefaultContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author jdy
 * @date 10:28 上午
 **/
@Slf4j
public class PointUtils {

    public static final ConcurrentHashMap<String, BigDecimal> PRECISION = new ConcurrentHashMap<>(8);
    public static final String NAN = String.valueOf(Double.NaN);
    public static final String CAL_DEVICE = "CAL_DEVICE";
    public static final String AGG_DEVICE = "AGG_DEVICE";
    public static final String AI = "AI";
    public static final String AO = "AO";
    public static final String DI = "DI";
    public static final String DO = "DO";
    public static final String VALUE = "value";
    public static final String V1 = "v1";
    public static final String V2 = "v2";

    private PointUtils() {
    }

    /**
     * 组合key使用
     *
     * @param deviceType
     * @param code
     * @return
     */
    public static String pointKey(String deviceType, String code) {
        return toPointKey(deviceType, code).toString();
    }

    public static String pointKey(String deviceType, String code, String suffix) {
        StringBuilder sb = toPointKey(deviceType, code);
        if (StringUtils.isNotBlank(suffix)) {
            sb.append(Utils.UL).append(suffix);
        }
        return sb.toString();
    }

    private static StringBuilder toPointKey(String deviceType, String code) {
        return new StringBuilder(Utils.DP_PRE).append(deviceType).append(Utils.UL).append(code);
    }

    public static BigDecimal getBigDecimal(String precision) {
        BigDecimal bigDecimal = PRECISION.get(precision);
        if (bigDecimal == null) {
            bigDecimal = new BigDecimal(precision);
            PRECISION.putIfAbsent(precision, bigDecimal);
        }
        return bigDecimal;
    }

    /**
     * 计算指定精度的值
     *
     * @param value
     * @param precision
     * @return
     */
    public static Double toPrecisionValue(String value, String precision) {
        if (StringUtils.isBlank(value) || StringUtils.equals(NAN, value)) {
            return null;
        }
        try {
            if (StringUtils.isBlank(precision)) {
                return Double.parseDouble(value);
            }
            final BigDecimal precisionValue = getBigDecimal(precision);
            if (precisionValue.doubleValue() <= 0.0) {
                //返回原值
                return Double.parseDouble(value);
            }
            return toPrecisionValue(new BigDecimal(value), precisionValue);
        } catch (Exception e) {
        }
        return null;
    }

    /**
     * 根据缩放因子zoom和精度precision处理数据
     *
     * @param value     当前值
     * @param zoom      缩放因子，扩大1+ 缩小1-
     * @param precision 精度1-
     * @return 最后的结果
     */
    public static Double toZoomAndPrecisionValue(String value, String zoom, String precision) {
        if (StringUtils.isBlank(value) || StringUtils.equals(NAN, value)) {
            return null;
        }
        try {

            if (StringUtils.isBlank(zoom) || StringUtils.equals("null", zoom)) {
                return toPrecisionValue(value, precision);
            }
            BigDecimal zoomDecimal = getBigDecimal(zoom);
            BigDecimal curValue = new BigDecimal(value).multiply(zoomDecimal, MathContext.DECIMAL64);

            if (StringUtils.isBlank(precision)) {
                return curValue.doubleValue();
            }
            final BigDecimal precisionValue = getBigDecimal(precision);
            if (precisionValue.doubleValue() <= 0.0) {
                //返回原值
                return curValue.doubleValue();
            }
            return toPrecisionValue(curValue, precisionValue);
        } catch (Exception e) {
        }
        return null;
    }

    private static Double toPrecisionValue(BigDecimal value, BigDecimal precisionValue) {
        return value
                .divide(precisionValue, 0, BigDecimal.ROUND_HALF_UP)
                .multiply(precisionValue).doubleValue();
    }

    /**
     * 是否为加工点位判断
     *
     * @param pointType 点位类型
     * @return true 加工点位，false 非加工点位
     */
    public static boolean isCal(String pointType) {
        return StringUtils.equals(pointType, AGG_DEVICE) || StringUtils.equals(pointType, CAL_DEVICE);
    }

    /**
     * 是否为跨设备加工聚合
     *
     * @param pointType 点位类型
     * @return true 加工点位，false 非加工点位
     */
    public static boolean isSpace(String pointType) {
        return StringUtils.equals(pointType, "AGG_SPACE") || StringUtils.equals(pointType, "CAL_SPACE") || StringUtils.equals(pointType, "CUSTOM");
    }


    /**
     * 是否为模拟量
     *
     * @param dataType 数据类型
     * @return true 模拟量，false 非模拟量
     */
    public static boolean isAi(String dataType) {
        return StringUtils.equals(dataType, AI) || StringUtils.equals(dataType, AO);
    }

    /**
     * 是否为状态量
     *
     * @param dataType 数据类型
     * @return true是，false 否
     */
    public static boolean isDi(String dataType) {
        return StringUtils.equals(dataType, DI) || StringUtils.equals(dataType, DO);
    }

    /**
     * 加工点位计算结果
     *
     * @param formula   表达式
     * @param valueMap  参数
     * @param precision 精度
     * @return 计算结果
     */
    public static Double calValue(String formula, Map<String, Double> valueMap, String precision) {
        if (StringUtils.isBlank(formula)){
            return null;
        }
        DefaultContext<String, Object> context = new DefaultContext<>();
        context.putAll(valueMap);
        try {
            Object objValue = QlExpressRunner.expressRunner
                    .execute(formula, context, null, true, false);
            if (objValue == null) {
                return null;
            }
            return toPrecisionValue(objValue.toString(), precision);
        } catch (Exception e) {
        }
        return null;
    }

    /**
     * 加工点位计算结果
     * @param dataType
     * @param formula
     * @param number
     * @return
     */
    public static Double exec(DataType dataType, String formula, Number number) {
        if (dataType == null || number == null) {
            return Double.NaN;
        }
        switch (dataType) {
            case TWO_REG_COM:
                return twoRegCom(formula, number.longValue());
            case THREE_REG_COM:
                return threeRegCom(formula, number.longValue());
            default:
                return exec(formula, number.doubleValue());
        }
    }


    /**
     * 加工点位计算结果
     *
     * @param formula 表达式
     * @return 计算结果
     */
    public static Double exec(String formula, Double value) {
        DefaultContext<String, Object> context = new DefaultContext<>();
        context.put(VALUE, value);
        try {
            Object objValue = QlExpressRunner.expressRunner
                    .execute(formula, context, null, true, false);
            if (objValue == null) {
                return Double.NaN;
            }
            return Double.parseDouble(objValue.toString());
        } catch (Exception e) {
        }
        return Double.NaN;
    }


    /**
     * 2个寄存器组合处理数据情况
     *
     * @param formula 表达式
     * @return 计算结果
     */
    public static Double twoRegCom(String formula, Long value) {
        if (value == null) {
            return Double.NaN;
        }
        int intV = value.intValue();
        short v2 = (short) (intV & 0xffff);
        short v1 = (short) ((intV >>> 16) & 0xffff);

        DefaultContext<String, Object> context = new DefaultContext<>();
        context.put(V1, v1);
        context.put(V2, v2);
        try {
            Object objValue = QlExpressRunner.expressRunner
                    .execute(formula, context, null, true, false);
            if (objValue == null) {
                return Double.NaN;
            }
            return Double.parseDouble(objValue.toString());
        } catch (Exception e) {
        }
        return Double.NaN;
    }

    /**
     * 3个寄存器组合处理数据情况
     * @param formula
     * @param value
     * @return
     */
    public static Double threeRegCom(String formula, Long value) {
        if (value == null) {
            return Double.NaN;
        }
        long part1 = (value >>> 16) & 0xffffffffL;
        int part2 = (int) (value & 0xffffL);
        DefaultContext<String, Object> context = new DefaultContext<>();
        context.put(V1, part1);
        context.put(V2, part2);
        try {
            Object objValue = QlExpressRunner.expressRunner
                    .execute(formula, context, null, true, false);
            if (objValue == null) {
                return Double.NaN;
            }
            return Double.parseDouble(objValue.toString());
        } catch (Exception e) {
            log.error("handler error {}", formula);
        }
        return Double.NaN;
    }
}
